package edu.nus.mtech.sem1.ca.data;

import static edu.nus.mtech.sem1.ca.utils.TelephonyConstants.COMMA;
import static edu.nus.mtech.sem1.ca.utils.TelephonyConstants.SEMICOLON;

import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import edu.nus.mtech.sem1.ca.cust.Customer;
import edu.nus.mtech.sem1.ca.cust.Status;
import edu.nus.mtech.sem1.ca.cust.complaint.Complaint;
import edu.nus.mtech.sem1.ca.exception.DataloadException;
import edu.nus.mtech.sem1.ca.plan.Account;
import edu.nus.mtech.sem1.ca.plan.Plan;
import edu.nus.mtech.sem1.ca.plan.valueadd.ValueAddedService;
import edu.nus.mtech.sem1.ca.utils.IOUtils;
import edu.nus.mtech.sem1.ca.utils.Logger;
import edu.nus.mtech.sem1.ca.utils.TelephonyConstants;
import edu.nus.mtech.sem1.ca.utils.TelephonyUtils;

/**
 * Stores system modified database data back into the csv file 
 *
 */
public class DatabasePersister {

	
	private static DatabasePersister singletonInstance;
	
	private static Properties applicationConfiguration=null;
	
	private Database database;
	
	public static DatabasePersister getInstance(Database database){
		if (singletonInstance==null){
			singletonInstance=new DatabasePersister(database);
			
		}
		return singletonInstance;
	}
	
		
	
	private DatabasePersister() throws DataloadException{}
	
	
	/**
	 * Called via singleton constructor. Loads configuration and prepares data for persisting
	 */
	public DatabasePersister(Database database) {
		//1.loads application configuration from properties file
		applicationConfiguration=ConfigurationUtils.loadConfiguration(); 
		this.database=database;
	}


	public void persist(){
		//Store customer and complaints info to database
		storeDataToFileSystem(database);
	}
	
	/**
	 * Core method. Transforms objects into String and stores data into filesystem
	 * @param database
	 */
	private void storeDataToFileSystem(Database database) {

		storeCustomerData(database); //store customer data into customers.csv
		storeCustomerComplaintData(database); //store complaint data into complaints.csv
		storeAccountPlanMappingData(database);
	}
	
	/**
	 * Returns value for a input key from the configuration file - in our case TelephonyApplication.properties
	 * @param key
	 * @return
	 */
	public static String getValueOfConfigurationKey(String key){
		//Loading from a property file. So, it is safe to assume that the value is always a String
		//Ah yes. unless the key is null
		if (key ==null){
			return ((String)applicationConfiguration.get(key));	
		}
		else{
			System.err.println(TelephonyConstants.ERR_KEY_NOT_FOUND_FOR_PROPERTY+"::"+key);
			
		}
		return "";
	}
	
	
	/**
	 * Loads customer data into database
	 * @param dbase
	 */
	private void storeCustomerData(Database dbase) {

		FileWriter fileWriter=null;
		BufferedWriter bufferedWriter = null;
		PrintWriter printWriter=null;
		
        try {
        	fileWriter=new FileWriter(System.getProperty("user.dir")+ "\\"+applicationConfiguration.getProperty(TelephonyConstants.CUSTOMER_DATA_FILE_LOCATION_KEY));
        	bufferedWriter = new BufferedWriter(fileWriter);
        	printWriter=new PrintWriter(bufferedWriter);
        	
        	List<String> customerInformationAsStringList=tranformCustomerDataIntoStringList(dbase.getCustomerMap());
        	
        	for(String eachCustomerInformationString:customerInformationAsStringList){
        		printWriter.println(eachCustomerInformationString);
        	}
            
        } catch (FileNotFoundException e) {
        	e.printStackTrace();
			System.err.println(TelephonyConstants.ERR_COULD_NOT_LOAD_APP_CONFIG+ e.getMessage());
			System.exit(1);
		} catch (IOException e) {
			e.printStackTrace();
			System.err.println(TelephonyConstants.ERR_COULD_NOT_LOAD_APP_CONFIG+ e.getMessage());
			System.exit(1);
		}catch (Exception e) {
			e.printStackTrace();
			System.err.println(TelephonyConstants.ERR_COULD_NOT_LOAD_APP_CONFIG+ e.getMessage());
			System.exit(1);
		}finally{
			//To avoid the too many files open. Not that it matters in a small application like this but still... you never know
			IOUtils.closeQuietly(printWriter);
			IOUtils.closeQuietly(bufferedWriter);
			IOUtils.closeQuietly(fileWriter);
		}
	}


	/**
	 * Transforms every customer object to a single line of string to be persisted to the database.
	 * @param customerMap
	 * @return
	 */
	private List<String> tranformCustomerDataIntoStringList(Map<String, Customer> customerMap) {
		
		List<String> customerDataAsStringList=null;
		//Check if the customer information is not null and size is not a huge zero
		if (customerMap!=null && customerMap.size()>0){
			Set<Entry<String, Customer>> customerSet = customerMap.entrySet();
			
			customerDataAsStringList=new ArrayList<String>();
			//Header
			//NRIC;NAME;ADDRESS;INTERESTS;EMAIL;STATUS;ACCOUNT_NUMBER
			//Yeah. Just add in the header. It has to come first. I guess that is why it is called header?
			customerDataAsStringList.add(TelephonyConstants.HEADER_CUSTOMER);
			
			StringBuilder eachCustomerDataAsString=null;
			Customer eachCustomer=null;
			for (Entry<String, Customer> eachCustomerEntry:customerSet){
				eachCustomer=eachCustomerEntry.getValue();
				
				if (eachCustomer!=null){
					eachCustomerDataAsString=new StringBuilder();
					//nric
					eachCustomerDataAsString.append(TelephonyUtils.getSafeString(eachCustomer.getNric())).append(SEMICOLON);
					//name
					eachCustomerDataAsString.append(TelephonyUtils.getSafeString(eachCustomer.getName())).append(SEMICOLON);
					//address
					eachCustomerDataAsString.append(TelephonyUtils.getSafeString(eachCustomer.getAddress())).append(SEMICOLON);
					//interests
					eachCustomerDataAsString.append(TelephonyUtils.getSafeString(eachCustomer.getInterests())).append(SEMICOLON);
					//email
					eachCustomerDataAsString.append(TelephonyUtils.getSafeString(eachCustomer.getEmail())).append(SEMICOLON);
					//status
					eachCustomerDataAsString.append(TelephonyUtils.getSafeString(eachCustomer.getStatus().toString())).append(SEMICOLON);
					//accountnumber
					eachCustomerDataAsString.append(TelephonyUtils.getSafeString(eachCustomer.getAccountNo()));
					
					customerDataAsStringList.add(eachCustomerDataAsString.toString());
				}
				
			}
			
		}
		
		return customerDataAsStringList;
	}

	/**
	 * Loads customer data into database
	 * @param dbase
	 */
	private void storeCustomerComplaintData(Database dbase) {

		FileWriter fileWriter=null;
		BufferedWriter bufferedWriter = null;
		PrintWriter printWriter=null;
		
        try {
        	fileWriter=new FileWriter(System.getProperty("user.dir")+ "\\"+applicationConfiguration.getProperty(TelephonyConstants.COMPLAINT_DATA_FILE_LOCATION_KEY));
        	bufferedWriter = new BufferedWriter(fileWriter);
        	printWriter=new PrintWriter(bufferedWriter);
        	
        	Map<String, Set<Complaint>> complaintMap = dbase.getComplaintMap();
        	List<String> complaintsDataIntoStringList = tranformComplaintsDataIntoStringList(complaintMap);
        	
        	for(String eachComplaintInformationString:complaintsDataIntoStringList){
        		printWriter.println(eachComplaintInformationString);
        	}
            
        } catch (FileNotFoundException e) {
        	e.printStackTrace();
			System.err.println(TelephonyConstants.ERR_COULD_NOT_LOAD_APP_CONFIG+ e.getMessage());
			System.exit(1);
		} catch (IOException e) {
			e.printStackTrace();
			System.err.println(TelephonyConstants.ERR_COULD_NOT_LOAD_APP_CONFIG+ e.getMessage());
			System.exit(1);
		}catch (Exception e) {
			e.printStackTrace();
			System.err.println(TelephonyConstants.ERR_COULD_NOT_LOAD_APP_CONFIG+ e.getMessage());
			System.exit(1);
		}finally{
			//To avoid the too many files open. Not that it matters in a small application like this but still... you never know
			IOUtils.closeQuietly(printWriter);
			IOUtils.closeQuietly(bufferedWriter);
			IOUtils.closeQuietly(fileWriter);
		}
	}


	/**
	 * Transforms every customer object to a single line of string to be persisted to the database.
	 * @param complaintMap
	 * @return
	 */
	private List<String> tranformComplaintsDataIntoStringList(Map<String, Set<Complaint>> complaintMap) {
		
		List<String> complaintsDataAsStringList=null;
		//Check if the customer information is not null and size is not a huge zero
		if (complaintMap!=null && complaintMap.size()>0){
			Set<Entry<String, Set<Complaint>>> customerComplaintEntrySet = complaintMap.entrySet();
			
			complaintsDataAsStringList=new ArrayList<String>();
			//Header
			//NRIC;ACCOUNT_NO;SERVICE;COMPLAINT_NO;COMPLAINT_DATE;COMPLAINT_DESC;STATUS
			complaintsDataAsStringList.add(TelephonyConstants.HEADER_COMPLAINTS);
			StringBuilder eachComplaintDataAsString=null;
			
			for (Entry<String, Set<Complaint>> eachCustomerComplaintEntry : customerComplaintEntrySet) {
				
				//eachComplaintDataAsString.append(eachCustomerComplaintEntry.getKey()).append(SEMICOLON);
				
				//Iterate through the complaints and transform to String
				for (Complaint eachComplaint:eachCustomerComplaintEntry.getValue()) {
					eachComplaintDataAsString=new StringBuilder();
					
					eachComplaintDataAsString.append(TelephonyUtils.getSafeString(eachCustomerComplaintEntry.getKey())).append(SEMICOLON);
					//nric
					eachComplaintDataAsString.append(TelephonyUtils.getSafeString(eachComplaint.getAccountNo())).append(SEMICOLON);
					//name
					eachComplaintDataAsString.append(TelephonyUtils.getSafeString(eachComplaint.getService().toString())).append(SEMICOLON);
					//address
					eachComplaintDataAsString.append(TelephonyUtils.getSafeString(eachComplaint.getComplaintNo())).append(SEMICOLON);
					//interests
					eachComplaintDataAsString.append(TelephonyUtils.getSafeString(TelephonyUtils.getStringFromDate(eachComplaint.getComplaintDate()))).append(SEMICOLON);
					//email
					eachComplaintDataAsString.append(TelephonyUtils.getSafeString(eachComplaint.getComplaintDesc())).append(SEMICOLON);
					//status
					eachComplaintDataAsString.append(TelephonyUtils.getSafeString(eachComplaint.getStatus().toString())).append(SEMICOLON);
					complaintsDataAsStringList.add(eachComplaintDataAsString.toString());
					
				}
				
				
			}
		}
		return complaintsDataAsStringList;
	}


	
	/**
	 * Loads customer data into database
	 * @param dbase
	 */
	private void storeAccountPlanMappingData(Database dbase) {

		FileWriter fileWriter=null;
		BufferedWriter bufferedWriter = null;
		PrintWriter printWriter=null;
		
        try {
        	fileWriter=new FileWriter(System.getProperty("user.dir")+ "\\"+applicationConfiguration.getProperty(TelephonyConstants.ACCOUNT_PLAN_MAPPING_DATA_FILE_LOCATION_KEY));
        	bufferedWriter = new BufferedWriter(fileWriter);
        	printWriter=new PrintWriter(bufferedWriter);
        	
        	Map<String, Account> accountMap = dbase.getAccountMap();
        	List<String> accountPlanDataIntoStringList = tranformAccountPlanMappingDataIntoStringList(accountMap);
        	
        	for(String eachAccountInformationString:accountPlanDataIntoStringList){
        		printWriter.println(eachAccountInformationString);
        	}
        	bufferedWriter.flush();
        	printWriter.flush();
        	fileWriter.flush();
            
        } catch (FileNotFoundException e) {
        	e.printStackTrace();
			System.err.println(TelephonyConstants.ERR_COULD_NOT_LOAD_APP_CONFIG+ e.getMessage());
			System.exit(1);
		} catch (IOException e) {
			e.printStackTrace();
			System.err.println(TelephonyConstants.ERR_COULD_NOT_LOAD_APP_CONFIG+ e.getMessage());
			System.exit(1);
		}catch (Exception e) {
			e.printStackTrace();
			System.err.println(TelephonyConstants.ERR_COULD_NOT_LOAD_APP_CONFIG+ e.getMessage());
			System.exit(1);
		}finally{
			//To avoid the too many files open. Not that it matters in a small application like this but still... you never know
			IOUtils.closeQuietly(printWriter);
			IOUtils.closeQuietly(bufferedWriter);
			IOUtils.closeQuietly(fileWriter);
		}
	}


	/**
	 * Transforms every customer object to a single line of string to be persisted to the database.
	 * @param accountMap
	 * @return
	 */
	private List<String> tranformAccountPlanMappingDataIntoStringList(Map<String, Account> accountMap) {
		
		List<String> accountDataAsStringList=null;
		//Check if the customer information is not null and size is not a huge zero
		if (accountMap!=null && accountMap.size()>0){
			Set<Entry<String, Account>> accountEntrySet = accountMap.entrySet();
			
			accountDataAsStringList=new ArrayList<String>();
			//Header
			//ACCOUNT_NUMBER,PLAN_ID,PLAN_TYPE,VALUE_ADDED_ID,VALUE_ADDED_TYPE,DATE_COMMENCED,DATE_TERMINATED,TELEPHONE_NUMBER,ADDITIONAL_CHANNELS,SUBSCRIPTION_STATUS
			accountDataAsStringList.add(TelephonyConstants.HEADER_ACCOUNT_PLAN_MAPPING);
			
			for (Entry<String, Account> eachAccountMappingEntry : accountEntrySet) {
				
				Account eachAccount=eachAccountMappingEntry.getValue();
				
				
				if (eachAccount.getPlans()!=null){
					
					for (Plan eachPlan:eachAccount.getPlans()){
						
						addPlanToStringList(accountDataAsStringList, eachAccount.getAccountNumber(), eachPlan);
						
						//Get value added services for a plan...
						
						if (eachPlan.getValueAddedServices()!=null){
							
							for (ValueAddedService eachVaService: eachPlan.getValueAddedServices()){
								addVaServiceToStringList(accountDataAsStringList, eachAccount.getAccountNumber(), eachPlan, eachVaService);
								
							}
							
						}
					}
					
				}
				
			}
		}
		return accountDataAsStringList;
	}
	
	
	private void addPlanToStringList(List<String> stringList, String accountNumber, Plan plan){
		
		Logger.log("String account number : "+accountNumber);
		//A00005,P00007,VOICE,,,3/11/2010,NA,96698676,,ACTIVE
		Logger.log("Current plan being persisted is :"+plan);
		StringBuilder eachPlanDataAsString=new StringBuilder();
		eachPlanDataAsString.append(accountNumber).append(COMMA);
		eachPlanDataAsString.append(plan.getPlanId()).append(COMMA);
		
		Logger.log("String list till now :: "+stringList);
		Logger.log("Current plan :: "+plan);
		eachPlanDataAsString.append(plan.getPlanType().toString()).append(COMMA);
		eachPlanDataAsString.append(COMMA).append(COMMA);//applicable only for VA Service
		eachPlanDataAsString.append(TelephonyUtils.getStringFromDate(plan.getDateCommenced())).append(COMMA);
		if (plan.getSubscriptionStatus().equals(Status.ACTIVE)){
			eachPlanDataAsString.append("NA").append(COMMA);	
		}
		else{
			eachPlanDataAsString.append(TelephonyUtils.getStringFromDate(plan.getDateTerminated())).append(COMMA);	
		}
		eachPlanDataAsString.append(plan.getTelephoneNumber()).append(COMMA);
		eachPlanDataAsString.append(COMMA); //applicable only for channel va
		eachPlanDataAsString.append(plan.getSubscriptionStatus().toString());
		
		stringList.add(eachPlanDataAsString.toString());
		
	}
	
	private void addVaServiceToStringList(List<String> stringList, String accountNumber, Plan plan, ValueAddedService vaService){
		
		//A00005,P00007,VOICE,V0003,IDD,1/11/2010,1/11/1931,90050969,,INACTIVE
		StringBuilder eachPlanDataAsString=new StringBuilder();
		eachPlanDataAsString.append(accountNumber).append(COMMA);
		eachPlanDataAsString.append(plan.getPlanId()).append(COMMA);
		eachPlanDataAsString.append(plan.getPlanType().toString()).append(COMMA);
		eachPlanDataAsString.append(vaService.getValueAddedId()).append(COMMA);
		eachPlanDataAsString.append(vaService.getValueAddedType().toString()).append(COMMA);
		eachPlanDataAsString.append(TelephonyUtils.getStringFromDate(vaService.getDateCommenced())).append(COMMA);
		if (vaService.getSubscriptionStatus().equals(Status.ACTIVE)){
			eachPlanDataAsString.append("NA").append(COMMA);	
		}
		else{
			eachPlanDataAsString.append(TelephonyUtils.getStringFromDate(vaService.getDateTerminated())).append(COMMA);	
		}
		eachPlanDataAsString.append(vaService.getTelephoneNumber()).append(COMMA);
		eachPlanDataAsString.append(vaService.getAdditionalChannels()).append(COMMA); //applicable only for channel va
		eachPlanDataAsString.append(vaService.getSubscriptionStatus().toString());
		
		stringList.add(eachPlanDataAsString.toString());
		
		
	}
}
